using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace AS3___CG
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;

        //For everything
        private Camera camera;
        private Camera cubeCamera;
        private InputManager im;

        Vector3 cUp;
        Vector3 cPos;
        Vector3 cTar;

        private RenderTargetCube rDTCube;

        //Effects
        private Effect baseEffects;
        private Effect bumpEffects;
        private BasicEffect basicEffect;
        private EnvironmentMapEffect envEffect;

        //AmbientLight and DiffuseLight and Specular Variables
        private Vector3 ambientLightColor;
        private Vector3 lightDirection;
        private Vector3 diffuseLightColor;
        private Vector4 specularColorPower;
        private Vector3 specularLightColor;

        //Fog Variables
        private Vector3 fogColor;
        private float fogStart;
        private float fogEnd;

        //Models
        private Model snowPlow;
        private Model sphereBumped;
        private Model sphereReflection;
        private Texture2D sphereTexture;
        private Texture2D sphereNormalMap;


        private List<ModelMesh> listOFTransparentParts;
        private List<Model> models;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            GraphicsDevice.RasterizerState = new RasterizerState() { CullMode = CullMode.None };
            //GraphicsDevice.GraphicsDeviceInformation.PresentationParameters.RenderTargetUsage = RenderTargetUsage.PreserveContents;
            camera = new Camera(GraphicsDevice);
            camera.InitCamera(45,GraphicsDevice.DisplayMode.AspectRatio);
            cubeCamera = new Camera(GraphicsDevice);
            cubeCamera.InitCamera(90,-1);

            im = new InputManager(this, camera);

            ambientLightColor = new Vector3(0.1f);
            lightDirection = new Vector3(-0.5f, -0.5f, -0.6f);
            lightDirection.Normalize();
            diffuseLightColor = new Vector3(1, 0.9f, 0.9f);
            //specularColorPower = new Vector4(1, 1, 1, 32f);
            //Sets the strength of the specular of the material, (could've used a list with many different strenghts to illustrate
            //different reflection strengths for differnt materials making the materials more natural (real world like)


            //Sets the color of the specular light
            specularLightColor = new Vector3(1, 0.9f, 0.9f);
            specularColorPower = new Vector4(1,1,1, 0f);
            //Fog variables
            fogColor = Color.CornflowerBlue.ToVector3(); //Keep this the same as backgroundColor
            fogStart = 30;
            fogEnd = 80;

            basicEffect = new BasicEffect(GraphicsDevice);
            basicEffect.World = Matrix.Identity;
            basicEffect.Projection = camera.projection;


            rDTCube = new RenderTargetCube(GraphicsDevice, 1024, false, SurfaceFormat.Color, DepthFormat.Depth16, 0,
                                           RenderTargetUsage.PreserveContents);
            


            envEffect = new EnvironmentMapEffect(GraphicsDevice);
            envEffect.World = Matrix.Identity;
            envEffect.Projection = camera.projection;
            envEffect.View = camera.GetLookAt();
            envEffect.EnableDefaultLighting();
           // envEffect.LightingEnabled = true;
            envEffect.FogEnabled = true;
            envEffect.FogStart = fogStart;
            envEffect.FogEnd = fogEnd;
            envEffect.FogColor = fogColor;
            envEffect.DiffuseColor = diffuseLightColor;
            envEffect.AmbientLightColor = ambientLightColor;
            envEffect.FresnelFactor = 0.2f;


           

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            listOFTransparentParts = new List<ModelMesh>();
            models = new List<Model>();

            snowPlow = Content.Load<Model>("snowPlow");
            snowPlow.Tag = "";

            sphereBumped = Content.Load<Model>("sphere_mapped2");
            sphereBumped.Tag = "Bumpy";

            sphereReflection = Content.Load<Model>("sphere_mapped");
            sphereReflection.Tag = "Reflection";


            sphereNormalMap = Content.Load<Texture2D>("normalMap");
            sphereTexture = Content.Load<Texture2D>("sphereTexture");

            bumpEffects = Content.Load<Effect>("Bumpy");
            baseEffects = Content.Load<Effect>("BaseEffects");

            models.Add(snowPlow);
            models.Add(sphereBumped);
           // models.Add(sphereReflection);

            //Add transparent mesh parts to a list
            foreach (Model model in models)
            {
                foreach (ModelMesh modelMesh in model.Meshes)
                {
                    foreach (ModelMeshPart modelMeshPart in modelMesh.MeshParts)
                    {
                        if (((BasicEffect)modelMeshPart.Effect).Alpha < 1)
                            listOFTransparentParts.Add(modelMesh);
                        //specularColorPower.W = ((BasicEffect)modelMeshPart.Effect).SpecularPower;
                        specularLightColor = ((BasicEffect)modelMeshPart.Effect).SpecularColor;
                        ambientLightColor = ((BasicEffect)modelMeshPart.Effect).AmbientLightColor;
                        diffuseLightColor = ((BasicEffect)modelMeshPart.Effect).DiffuseColor;
                    }
                }
            }
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            im.getAction(gameTime);
            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            #region reflectionCube


            cubeCamera.cPos = Vector3.Zero;
            //  camera._aspectRatio = -1;


            for (int i = 0; i < 6; i++)
            {
                GraphicsDevice.SetRenderTarget(rDTCube, (CubeMapFace)i);

                GraphicsDevice.Clear(Color.CornflowerBlue);    
            }


            //cubeface1, left side
            GraphicsDevice.SetRenderTarget(rDTCube, CubeMapFace.NegativeX);

            cubeCamera.cTar = Vector3.Left;
            cubeCamera.cUp = Vector3.Up;

            foreach (Model m in models)
                DrawModel(m, false, cubeCamera);

            foreach (Model m in models)
                DrawModel(m, true, cubeCamera);

            //cubeface2, bottom side
            GraphicsDevice.SetRenderTarget(rDTCube, CubeMapFace.NegativeY);


            cubeCamera.cTar = Vector3.Down;
            cubeCamera.cUp = Vector3.Forward;

            foreach (Model m in models)
                DrawModel(m, false,cubeCamera);

            foreach (Model m in models)
                DrawModel(m, true, cubeCamera);

            //cubeface3, forward side
            GraphicsDevice.SetRenderTarget(rDTCube, CubeMapFace.NegativeZ);


            cubeCamera.cTar = Vector3.Forward;
            cubeCamera.cUp = Vector3.Up;

            foreach (Model m in models)
                DrawModel(m, false, cubeCamera);

            foreach (Model m in models)
                DrawModel(m, true, cubeCamera);

            //cubeface4, right side
            GraphicsDevice.SetRenderTarget(rDTCube, CubeMapFace.PositiveX);


            cubeCamera.cTar = Vector3.Right;
            cubeCamera.cUp = Vector3.Up;

            foreach (Model m in models)
                DrawModel(m, false, cubeCamera);

            foreach (Model m in models)
                DrawModel(m, true, cubeCamera);

            //cubeface5, top side
            GraphicsDevice.SetRenderTarget(rDTCube, CubeMapFace.PositiveY);


            cubeCamera.cTar = Vector3.Up;
            cubeCamera.cUp = Vector3.Forward;

            foreach (Model m in models)
                DrawModel(m, false, cubeCamera);

            foreach (Model m in models)
                DrawModel(m, true, cubeCamera);

            //cubeface6, backward side
            GraphicsDevice.SetRenderTarget(rDTCube, CubeMapFace.PositiveZ);


            cubeCamera.cTar = Vector3.Backward;
            cubeCamera.cUp = Vector3.Up;

            foreach (Model m in models)
                DrawModel(m, false, cubeCamera);

            foreach (Model m in models)
                DrawModel(m, true, cubeCamera);

            // reseting rendertarget
            
            GraphicsDevice.SetRenderTarget(null);

            
            envEffect.EnvironmentMap = rDTCube;

            //put back camera properties

            #endregion




            GraphicsDevice.Clear(Color.CornflowerBlue);

            //Draw opaque parts
            foreach (Model m in models)
                DrawModel(m, false, camera);

            //Draw transparent parts
            foreach (Model m in models)
                DrawModel(m, true, camera);

            DrawModel(sphereReflection, false, camera);

            base.Draw(gameTime);
        }

        private void DrawModel(Model model, bool drawTransparentparts, Camera camera)
        {
            Effect effect;

            if (model.Tag.Equals("BaseEffects"))
                effect = baseEffects;
            else
                effect = bumpEffects;

            if (drawTransparentparts)
            {
                GraphicsDevice.BlendState = BlendState.AlphaBlend;
                GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
            }
            else
            {
                GraphicsDevice.BlendState = BlendState.Opaque;
                GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            }

            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    if (model.Tag.Equals("Bumpy") || model.Tag.Equals("baseEffects"))
                    {
                        effect.Parameters["SpecularLightColor"].SetValue(specularLightColor);
                        effect.Parameters["SpecularColorPower"].SetValue(specularColorPower);
                        //Why did it use this one?
                        //effect.Parameters["DiffuseLightColor"].SetValue(((BasicEffect)part.Effect).DiffuseColor);
                        effect.Parameters["DiffuseLightColor"].SetValue(diffuseLightColor);
                        effect.Parameters["AmbientLightColor"].SetValue(ambientLightColor);
                        effect.Parameters["View"].SetValue(camera.GetLookAt());
                        effect.Parameters["Projection"].SetValue(camera.projection);
                        effect.Parameters["FogColor"].SetValue(fogColor);
                        effect.Parameters["FogEnd"].SetValue(fogEnd);
                        effect.Parameters["FogStart"].SetValue(fogStart);
                        effect.Parameters["CameraPosition"].SetValue(camera.cPos);
                        effect.Parameters["LightDirection"].SetValue(-lightDirection);
                        effect.Parameters["World"].SetValue(Matrix.Identity * Matrix.CreateTranslation(-5, 2, 2));

                        if (model.Tag.ToString().Equals("Bumpy"))
                        {
                            effect.Parameters["NormalMap"].SetValue(sphereNormalMap);
                            effect.Parameters["ModelTexture"].SetValue(sphereTexture);
                        }
                    }
                }
            }

            if (model.Tag.Equals(""))
            {
                foreach (ModelMesh mesh in model.Meshes)
                {
                    foreach (BasicEffect be in mesh.Effects)
                    {

                        be.World = Matrix.Identity * Matrix.CreateTranslation(3, 3, 3);
                        be.Projection = camera.projection;
                        be.View = camera.GetLookAt();
                        be.EnableDefaultLighting();
                        be.LightingEnabled = true;
                        be.FogEnabled = true;
                        be.FogStart = fogStart;
                        be.FogEnd = fogEnd;
                        be.FogColor = fogColor;
                        be.SpecularColor = specularLightColor;
                        be.SpecularPower = specularColorPower.W;

                        if ((listOFTransparentParts.Contains(mesh) && drawTransparentparts) ||
                            !listOFTransparentParts.Contains(mesh) && !drawTransparentparts)
                            mesh.Draw();
                    }
                }
            }
            else if (model.Tag.Equals("Bumpy"))
            {
                foreach (ModelMesh modelMesh in model.Meshes)
                {
                    foreach (ModelMeshPart modelMeshPart in modelMesh.MeshParts)
                    {
                        modelMeshPart.Effect = effect;
                        // ((EnvironmentMapEffect) modelMeshPart.Effect).World = Matrix.Identity*Matrix.CreateTranslation(0,0,10);
                        //  ((EnvironmentMapEffect) modelMeshPart.Effect).Projection = camera.projection;
                        //  ((EnvironmentMapEffect) modelMeshPart.Effect).View = camera.GetLookAt();
                    }
                    modelMesh.Draw();
                }

            }
            else
            {
                foreach (ModelMesh modelMesh in model.Meshes)
                {
                    foreach (ModelMeshPart modelMeshPart in modelMesh.MeshParts)
                    {
                        modelMeshPart.Effect = envEffect;
                        ((EnvironmentMapEffect)modelMeshPart.Effect).World = Matrix.Identity * Matrix.CreateTranslation(0, 0, 0);
                        ((EnvironmentMapEffect)modelMeshPart.Effect).Projection = camera.projection;
                        ((EnvironmentMapEffect)modelMeshPart.Effect).View = camera.GetLookAt();
                    }
                    modelMesh.Draw();
                }
            }

        }
    }
}